package com.wt.test.proxy;

import java.lang.reflect.*;

/**
 * @description
 * @author: wangtao
 * @date:9:19 2019/3/8
 * @email:taow02@jumei.com
 */
public class JDKProxyToStringQuestion {

	public static void main(String[] args)
			throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
		//创建代理类的时候会调用ProxyGenerator，该类可以将生成代理类class输出到文件，具体细节可以看ProxyGenerator.generateProxyClass(...)方法
		//方法中的saveGeneratedFiles常量就是读取的sun.misc.ProxyGenerator.saveGeneratedFiles值
		System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
		Object o = Proxy.newProxyInstance(A.class.getClassLoader(), new Class[]{A.class}, new B());
		Constructor<?> constructor = o.getClass().getConstructor(InvocationHandler.class);
		((A) o).say();
		Object o1 = constructor.newInstance(new B2());
		System.out.println(o.toString());
		System.out.println(o1.toString());
		((A)o1).say();
		Method toString = Object.class.getMethod("toString");
		C c = new C();
		System.out.println(toString.invoke(c));
		System.out.println(c);
		System.out.println(c.getClass().getName() + "@" + Integer.toHexString(c.hashCode()));
	}

}

interface A {
	void say();
}

class C {
	@Override
	public String toString() {
		return super.toString();
	}
}

class B implements InvocationHandler {

	//Before implements Before
	//After implements After
	//Around implements Before,After
	//	private BeforeHandler before;
	//	private AfterHandler after;
	//	private AroundHandler aroundHandler;

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//toString会导致递归，栈溢出，所以这里如果方法是Object对象中的方法，就直接调用当前对象的toString。可以参考Mybatis的代理类处理方式。
		//这里的代理方式是只有接口，没有实现类，如果代理的不是接口，并且代理的是实现类时候，这里可以使用实现类实例作为调用对象来处理递归死循环。
		//这里的method是Object的，因为Object是所有类的父类，所以，method.invoke()的第一个参数（被调用的对象）可以是任意对象。
		//当然，生成的代理类$Proxy0跟当前的InvocationHandler并没有任何关系，调用toString也就相当只是执行了一个new InvocationHandler().toString()而已。仅仅是解决了栈溢出的问题
		//如果想得到代理类的标准toString结果（class全限定类名@hashCode），可以仿照Object.toString()重新写一次toString，如下
		if (method.getName().equals("toString")) {
			return proxy.getClass().getName() + "@" + Integer.toHexString(proxy.hashCode());
		}
		if (method.getDeclaringClass() == Object.class) {
			return method.invoke(this, args);
		}
		if (method.getName().equals("say")) {
			System.out.println("hi");
			return null;
		}
		return method.invoke(this, args);
	}
}

class B2 implements InvocationHandler {

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//toString会导致递归，栈溢出，所以这里如果方法是Object对象中的方法，就直接调用当前对象的toString。可以参考Mybatis的代理类处理方式。
		//这里的代理方式是只有接口，没有实现类，如果代理的不是接口，并且代理的是实现类时候，这里可以使用实现类实例作为调用对象来处理递归死循环。
		//这里的method是Object的，因为Object是所有类的父类，所以，method.invoke()的第一个参数（被调用的对象）可以是任意对象。
		//当然，生成的代理类$Proxy0跟当前的InvocationHandler并没有任何关系，调用toString也就相当只是执行了一个new InvocationHandler().toString()而已。仅仅是解决了栈溢出的问题
		//如果想得到代理类的标准toString结果（class全限定类名@hashCode），可以仿照Object.toString()重新写一次toString，如下
		if (method.getName().equals("toString")) {
			return proxy.getClass().getName() + "@" + Integer.toHexString(proxy.hashCode());
		}
		if (method.getDeclaringClass() == Object.class) {
			return method.invoke(this, args);
		}
		if (method.getName().equals("say")) {
			System.out.println("hi B2");
			return null;
		}
		return method.invoke(this, args);
	}
}